package concordia.comp6231.rfmulticast;

import java.io.IOException;
import java.math.BigInteger;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.zip.CRC32;
import java.util.zip.Checksum;

public class ReplyHandler extends Thread {
	
	private int port;
	private ListernerHandler rh;
	private DatagramSocket aSocket = null;
	private DatagramPacket request = null;
	private byte[] buffer = new byte[1000];
	private String finalizedCRCNumber;
	private long packetSequenceNumber;
	private final String COMMA = ",";
	private final String POINT = ".";
	private final String VERTICAL_LINE = "|";
	private Message constructedMessage = null;
	public final String RFM_MSG_SENDER = "SenderName";
	public final String RFM_MSG_DEAD_HOST = "deadName";
	private final String POS_CRC_ACK = "+veCRC";
	private final String NEG_CRC_ACK = "-ve_ACK_CRC";
	int count = 2;
	int cc = 4;
	
	private Message responseMsg = null;
	private Queue<Message> holdBackQueue = null;
	private boolean QueueFlag = false;
	
	protected ReplyHandler(int port,ListernerHandler rh){
		this.port = port;
		this.rh = rh;
		constructedMessage = new Message();
		holdBackQueue = new LinkedList<Message>();
	}
	protected ReplyHandler(){
	}
	
	public void run(){
		/*
		try {
			aSocket = new DatagramSocket(port);
		} catch (SocketException e1) {
			System.err.println("	>Port already in use: " + port);
			e1.printStackTrace();
		}
		*/
		while(true){
			
				receive();
				
	    		String content = new String(request.getData());
	  			System.out.println("	>The content is: "+content);
	  			
	  			if(request.getLength() == 5){
	  				System.out.println("	>its alive message");
	  				//I will move it later
	  				aSocket.close();
	  				replyBack("I'm alive".getBytes());
	  			}
	  			else{ 
	  				System.out.println("	>its a real message");
	  				
	  				byte packet[] = request.getData();
	  				constructedMessage = UnMarshallPlease(packet);
	  			    long calculatedValue = CalculateCheckSum(content);
	  				System.out.println("	>calculated CRC value: "+calculatedValue);
	  			
	  				 //Used ony for me for testing the corrupted packets
	  				//if(cc > 0) {
	  				//calculatedValue++;
	  				//	cc--;
	  				//}
	  				
	  				
	  				if(Corrupted(calculatedValue)){
	  					System.out.println("	>packet corrupted!  LOCAL");
	  					aSocket.close();
	  					replyBack(NEG_CRC_ACK.getBytes());
		  				run();
	  				}
	  				else{
	  					replyBack(POS_CRC_ACK.getBytes());
	  				
	  					System.out.println("	>good packet!");
	  					System.out.println("	>receive S: "+packetSequenceNumber);
	  					System.out.println("	>local S: "+RequestHandler.getLocalSequenceNumner());
	  					
	  					if(packetSequenceNumber == RequestHandler.getLocalSequenceNumner()    ){
	  						System.out.println("	>RIGHT S #");
	  						
	  						replyBack("good S #.".getBytes());
	  						
	  						
	  					System.out.println("	>New functionality from here:");	
	  					
	  					responseMsg =  rh.handleResult(constructedMessage);
	  					byte MarshalledForm[] = new RequestHandler().MarshallPlease(responseMsg);
	  					//i will move it later
	  					aSocket.close();
	  					/*
	  					replyBack(MarshalledForm); 
	  					*/
	  					
	  					try{
	  						aSocket = new DatagramSocket();
	  						
	  						
	  						System.out.println("The port is: "+port);
	  						System.out.println("The port is(request): "+request.getPort());
	  						
	  						//aSocket = new DatagramSocket(port);
	  						DatagramPacket reply = new DatagramPacket(MarshalledForm, MarshalledForm.length, request.getAddress(), port);
	  						aSocket.send(reply);
	  				  		
	  					}catch(SocketException e){System.out.println("	>Socket list: " + e.getMessage());
	  					}catch(IOException e) {System.out.println("	>IO: " + e.getMessage());
	  					}finally {if(aSocket != null) aSocket.close();}
	  					
	  					System.out.println("	>Round trip completed!");
	  						
	  					//if(aSocket != null) aSocket.close();
	  					
	  					System.out.println("	>Checking the hold-back-queue..");
	  					
	  					if(QueueFlag){
	  						 responseMsg = rh.handleResult(holdBackQueue.poll());
	  						//byte MarshalledForm[] = new RequestHandler().MarshallPlease(responseMsg);
		  					//i will move it later
		  					aSocket.close();
		  					replyBack(MarshalledForm); 
		  					System.out.println("	>processing Message from hold-back-queue finished");
		  					
	  						
	  					}
	  						
	  					}
	  					else if(packetSequenceNumber > RequestHandler.getLocalSequenceNumner() + 1){
	  						System.out.println("	>will be putted in the hold-back queue!");
	  						
	  						replyBack("I got the next packet, no problem (S #)".getBytes());
	  						holdBackQueue.add(constructedMessage);
	  						QueueFlag = true;
	  						
	  						
	  						
	  					}
	  					else {
	  						System.out.println("	>Duplicated packet! I will not process it.");
	  					}
	  					
	  				//}
	  				}
	  			}
	  		/*	
	  			DatagramPacket reply = new DatagramPacket(request.getData(), request.getLength(), 
	    		request.getAddress(), request.getPort());
	  			aSocket.send(reply); */
	  		
			//}catch(SocketException e){System.out.println("Socket list: " + e.getMessage());
			//}catch(IOException e) {System.out.println("IO: " + e.getMessage());
			//}finally {if(aSocket != null) aSocket.close();} 
			aSocket.close();
			}
		
	}
	
	private void receive(){
		try{
			
		aSocket = new DatagramSocket(port);
		request = new DatagramPacket(buffer, buffer.length);
		System.out.println("	>WAITING....");
		aSocket.receive(request);     
		System.out.println("	>listener received now");
			
		
		}catch(SocketException e){System.out.println("	>Socket list: " + e.getMessage());
		}catch(IOException e) {System.out.println("	>IO: " + e.getMessage());
		}//finally {if(aSocket != null) aSocket.close();} 
	
	}
	
	private void replyBack(byte replyMessage[]){
		//aSocket.close();
		//byte replyMsg[] = replyMessage.getBytes();
		try{
			aSocket = new DatagramSocket(port);
			DatagramPacket reply = new DatagramPacket(replyMessage, replyMessage.length, 
			request.getAddress(), request.getPort());
			aSocket.send(reply);
	  		
		}catch(SocketException e){System.out.println("	>Socket list: " + e.getMessage());
		}catch(IOException e) {System.out.println("	>IO: " + e.getMessage());
		}finally {if(aSocket != null) aSocket.close();}
		
	}
	
	private long CalculateCheckSum(String content){
		
		content = content.substring(0,content.indexOf("|")+1);
		System.out.println("	>inside calculated CRC value: "+content);
		byte bytes[] = content.getBytes();
		
		Checksum checkSum = new CRC32();
		checkSum.update(bytes,0,bytes.length);
		long checkSumValue = checkSum.getValue();
		System.out.println("	>the calculated value of check sum: " + checkSumValue);
		
		return checkSumValue;
		
	}
	
	protected Message UnMarshallPlease(byte[] packet){
		String receivedBytes = new String(packet);
		int lastIndex = 0;
		String givenCRCNumber = null;
		String extractedSequenceNumber = null;
		Message tmpMessage = new Message();
		
		System.out.println("	>THE PACKET IS: "+new String(packet));
		
		lastIndex = receivedBytes.indexOf(VERTICAL_LINE);
		givenCRCNumber = receivedBytes.substring(++lastIndex,receivedBytes.length());
		receivedBytes = receivedBytes.substring(0,lastIndex);
		System.out.println("	>After removing CRC #: "+receivedBytes);
		/* to remove any leading character */
		
		finalizedCRCNumber = givenCRCNumber.substring(0, givenCRCNumber.indexOf(VERTICAL_LINE));
		System.out.println("	>Given CRC #: "+finalizedCRCNumber);
		
		extractedSequenceNumber = receivedBytes.substring(receivedBytes.indexOf(POINT)+1,receivedBytes.indexOf(VERTICAL_LINE));
		//packetSequenceNumber = Long.parseLong(extractedSequenceNumber);
		packetSequenceNumber = Long.parseLong("0");
		System.out.println("	>as Long: "+packetSequenceNumber);
		
		receivedBytes = receivedBytes.substring(0,receivedBytes.indexOf(POINT));
		System.out.println("	>After removing CRC #: "+receivedBytes);
		
		String token[] = receivedBytes.split(",");
		
		System.out.println("	>TOKENS ARE:");
		for(int i=0; i<token.length; i = i+2){
			token[i] = token[i].trim();
			if (i+1 < token.length) {
				token[i+1] = token[i+1].trim();
				System.out.println("	>"+token[i]);
				tmpMessage.put(token[i], token[i+1]);
			}
		}
		
		//String total = "";
		System.out.println("	>Now verifying the new constructed messgae:");
		for (String key : tmpMessage.keySet()) {
			System.out.println("	>key is: " + key + " and value is: " + tmpMessage.get(key));
		}
		
		return tmpMessage;
		
	}
	
	private boolean Corrupted(long calculatedValue){
		if(calculatedValue != Long.parseLong(finalizedCRCNumber)) return true;
		else return false;	
	}
	
}
